package com.googlecode.classgrep.utils;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jw.asmsupport.utils.StringUtils;

import org.apache.commons.collections.ListUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.objectweb.asm.Type;

import com.googlecode.classgrep.classloader.ClassLoaderInterface;
import com.googlecode.classgrep.exception.ReflectionException;

public class CommonUtils {
	
    private static final Log LOG = LogFactory.getLog(CommonUtils.class);
    
    /** 基本类型Class */
	static Map<String, Class<?>> primitivesToClasses;

    static
    {
		HashMap<String, Class<?>> tmp = new HashMap<String, Class<?>>();
		tmp.put( "boolean", boolean.class );
		tmp.put( "int", int.class );
		tmp.put( "char", char.class );
		tmp.put( "short", short.class );
		tmp.put( "int", int.class );
		tmp.put( "long", long.class );
		tmp.put( "float", float.class );
		tmp.put( "double", double.class );
		tmp.put( "void", void.class );
		
		/*class description*/
		tmp.put( "Z", boolean.class );
		tmp.put( "B", byte.class );
		tmp.put( "C", char.class );
		tmp.put( "S", short.class );
		tmp.put( "I", int.class );
		tmp.put( "J", long.class );
		tmp.put( "F", float.class );
		tmp.put( "D", double.class );
		tmp.put( "V", void.class );

	    primitivesToClasses = Collections.unmodifiableMap( tmp );
    }

    public static String javaName(String name) {
        return (name == null)? null:name.replace('/', '.');
    }
    
    public static List<URL> getURLs(ClassLoaderInterface classLoader, String[] dirNames) {
        List<URL> urls = new ArrayList<URL>();
        for (String dirName : dirNames) {
            try {
                Enumeration<URL> classLoaderURLs = classLoader.getResources(dirName);
                while (classLoaderURLs.hasMoreElements()) {
                    URL url = classLoaderURLs.nextElement();
                    urls.add(url);
                }
            } catch (IOException ioe) {
                if (LOG.isErrorEnabled())
                    LOG.error("Could not read driectory ["+dirName+"]", ioe);
            }
        }

        return urls;
    }

    public static Collection<URL> getUrls(ClassLoaderInterface classLoaderInterface, boolean excludeParent) throws IOException {
        return getUrls(classLoaderInterface, excludeParent? classLoaderInterface.getParent() : null);
    }

    public static Collection<URL> getUrls(ClassLoaderInterface classLoader, ClassLoaderInterface excludeParent) throws IOException {
        UrlSet urlSet = new UrlSet(classLoader);
        if (excludeParent != null){
            urlSet = urlSet.exclude(excludeParent);
        }
        return urlSet.getUrls();
    }
    
    
    public static Class<?> forName(String className) throws ClassNotFoundException{
    	return forName(className, true, Thread.currentThread().getContextClassLoader());
    }
    
	public static Class<?> forName(String className, boolean init, ClassLoader loader) throws ClassNotFoundException {
		try {
			
			Class<?> clazz = primitivesToClasses.get(className);
			if(clazz != null){
				return clazz;
			}
			
			if(className.startsWith("[")){
				className = StringUtils.replace(className, "/", ".");
			}else if(className.startsWith("L")){
				className = StringUtils.substring(className, 1, className.length() - 1);
				className = StringUtils.replace(className, "/", ".");
			}
			
			return Class.forName(className, true, loader);
		} catch (ClassNotFoundException e) {
			Class<?> cls = (Class<?>) primitivesToClasses.get(className);
			if(cls != null){
				return cls;
			}else{
				//chech for internal name
				className = StringUtils.replace(className, "/", ".");
				try{
					return Class.forName(className, true, loader);
				} catch (ClassNotFoundException e1) {
					throw e;
				}
			}
		}
	}
	
	public static Map unmodifiableMap(Map map){
	    if(map == null){
	        return MapUtils.EMPTY_MAP;
	    }else{
	        return MapUtils.unmodifiableMap(map);
	    }
	}
	
	public static List unmodifiableList(List list){
	    if(list == null){
	        return ListUtils.EMPTY_LIST;
	    }else{
	        return Collections.unmodifiableList(list);
	    }
	}
	
	
	
	/**
	 * 
	 * @param clazz
	 * @param name
	 * @param paramTypes
	 * @return
	 * @throws NoSuchMethodException
	 */
	public static Method reflect2Method(Class<?> clazz, String name, Class<?>... paramTypes) throws NoSuchMethodException{
		return clazz.getDeclaredMethod(name, paramTypes);
	}

	
	
	/**
	 * 
	 * @param clazz
	 * @param paramTypes
	 * @return
	 * @throws NoSuchMethodException
	 */
	public static Constructor<?> reflect2Constructor(Class<?> clazz, Class<?>... paramTypes) throws NoSuchMethodException{
		return clazz.getDeclaredConstructor(paramTypes);
	}
	
	
	/**
	 * 
	 * @param owner
	 * @param name
	 * @param funDesc
	 * @param classloader
	 * @return
	 * @throws ClassNotFoundException
	 * @throws NoSuchMethodException
	 */
	private static Object reflect2BytecodeMethod(Class<?> owner, String name, String funDesc, ClassLoader classloader) throws ClassNotFoundException, NoSuchMethodException{
		//Class<?> callingFunOwnerCls = CommonUtils.forName(owner, true, classloader);
		Type[] argTypes = Type.getArgumentTypes(funDesc);
		Class<?>[] argClass = new Class<?>[argTypes.length];
		for(int i=0, len = argTypes.length; i<len; i++){
			argClass[i] = CommonUtils.forName(argTypes[i].getDescriptor(), true, classloader);
		}
		
		if (name.equals("<init>")) {
			
			return reflect2Constructor(owner, argClass);
		
		}else if(name.equals("<clinit>")){
			
		    throw new ReflectionException("Cannot get static block by reflect at class : " + owner);
			
		}else{

			return reflect2Method(owner, name, argClass);
		}
	}
	

	/**
	 * 
	 * @param clazzDesc
	 * @param name
	 * @param methodDesc
	 * @return
	 * @throws ClassNotFoundException
	 * @throws NoSuchMethodException
	 */
	public static Method reflect2Method(String clazzDesc, String name, String methodDesc) throws ClassNotFoundException, NoSuchMethodException{
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		return (Method) reflect2BytecodeMethod(CommonUtils.forName(clazzDesc, true, classLoader), name, methodDesc, classLoader);
	}
	

	
	/**
	 * 
	 * @param clazzDesc
	 * @param name
	 * @param methodDesc
	 * @param classLoader
	 * @return
	 * @throws ClassNotFoundException
	 * @throws NoSuchMethodException
	 */
	public static Method reflect2Method(String clazzDesc, String name, String methodDesc, ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException{
		return (Method) reflect2BytecodeMethod(CommonUtils.forName(clazzDesc, true, classLoader), name, methodDesc, classLoader);
	}
	

	/**
	 * 
	 * @param clazzDesc
	 * @param methodDesc
	 * @return
	 * @throws NoSuchMethodException
	 * @throws ClassNotFoundException
	 */
	public static Constructor<?> reflect2Constructor(String clazzDesc, String methodDesc) throws NoSuchMethodException, ClassNotFoundException{
		return reflect2Constructor(clazzDesc, methodDesc, Thread.currentThread().getContextClassLoader());
	}
	

	/**
	 * 
	 * @param clazzDesc
	 * @param methodDesc
	 * @param classLoader
	 * @return
	 * @throws NoSuchMethodException
	 * @throws ClassNotFoundException
	 */
	public static Constructor<?> reflect2Constructor(String clazzDesc, String methodDesc, ClassLoader classLoader) throws NoSuchMethodException,ClassNotFoundException{
		return (Constructor<?>) reflect2BytecodeMethod(CommonUtils.forName(clazzDesc, true, classLoader), "<init>", methodDesc, classLoader);
	}
	
	/**
	 * force invoke method even the method is private.
	 * 
	 * @param owner
	 * @param method
	 * @param parameters
	 * @return
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public static Object forceInvokeMethod(Object owner, Method method, Object... parameters) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		boolean accessiable = method.isAccessible();
		method.setAccessible(true);
		Object obj = method.invoke(owner, parameters);
		method.setAccessible(accessiable);
		return obj;
	}
}
